#include "qe_log.h"
#include "qe_def.h"
#include "qe_metric.h"
#include "qe_string.h"



#define DV_KB   (1024.0)
#define DV_MB   ((DV_KB) * (DV_KB))
#define DV_GB   ((DV_KB) * (DV_MB))

#define DV_MIN  0.0
#define DV_MAX  1.79769e+308

#define DELAY_MS    (1000)
#define DELAY_SEC   (1000000)

#define SEC_TO_MS(sec)      (sec * 1000)
#define MS_TO_SEC(ms)       (ms / 1000.0)
#define BYTES_TO_BITS(bytes)    (bytes * 8)
#define BITS_TO_BYTES(bits)     (bits / 8)



static void metric_dv_init(qe_metric_dv *dv)
{
    dv->val  = DV_MIN;
    dv->min  = DV_MAX;
    dv->max  = DV_MIN;
}

static void metric_dv_update(qe_metric_dv *dv, double v)
{
    if (dv->min > v) {
        dv->min = v;
    }

    if (dv->max < v) {
        dv->max = v;
    }

    dv->val = v;
}

static double metric_dv_get_val(qe_metric_dv *dv)
{
    return dv->val;
}

static double metric_dv_get_min(qe_metric_dv *dv)
{
    return dv->min;
}

static double metric_dv_get_max(qe_metric_dv *dv)
{
    return dv->max;
}

qe_const_str qe_bps_str(double bps)
{
    static char string[16] = {'\0'};
    if (bps >= DV_GB) {
        qe_sprintf(string, "%.02fGbps", bps / DV_GB);
    } else if (bps >= DV_MB) {
        qe_sprintf(string, "%.02fMbps", bps / DV_MB);
    } else if (bps >= DV_KB) {
        qe_sprintf(string, "%.02fKbps", bps / DV_KB);
    } else {
        qe_sprintf(string, "%.02fbps", bps);
    }

    return string;
}

qe_const_str qe_pps_str(double pps)
{
    static char string[16] = {'\0'};
    if (pps >= DV_GB) {
        qe_sprintf(string, "%.02fGpps", pps / DV_GB);
    } else if (pps >= DV_MB) {
        qe_sprintf(string, "%.02fMpps", pps / DV_MB);
    } else if (pps >= DV_KB) {
        qe_sprintf(string, "%.02fKpps", pps / DV_KB);
    } else {
        qe_sprintf(string, "%.02fpps", pps);
    }

    return string;
}

static qe_const_str per_str(double rate)
{
    static char string[16] = {'\0'};
    qe_sprintf(string, "%.4f", rate);
    return string;
}

static qe_const_str delay_str(double delay)
{
    static char string[16] = {'\0'};

    if (delay >= DELAY_SEC) {
        qe_sprintf(string, "%.02fs", delay / DELAY_SEC);
    } else if (delay >= DELAY_MS) {
        qe_sprintf(string, "%.02fms", delay / DELAY_MS);
    } else {
        qe_sprintf(string, "%.02fus", delay);
    }

    return string;
}

void qe_metric_bps_init(qe_metric_bps *metric, qe_u32 period_sec)
{
    metric->pts = 0;
    metric->bytes = 0;
    metric->period_sec = period_sec;
    metric_dv_init(&metric->bps);
}

qe_bool qe_metric_bps_update(qe_metric_bps *metric, qe_u64 bytes)
{
    double bps;
    qe_time_t pts;

    pts = qe_time_ms();

    metric->bytes += bytes;

    if (!metric->pts) {
        metric->pts = pts;
        return qe_false;
    }

    if ((pts - metric->pts) < SEC_TO_MS(metric->period_sec)) {
        return qe_false;
    }

    metric->pts = pts;

    bps = (double)BYTES_TO_BITS(metric->bytes) / (double)metric->period_sec;
    metric->bytes = 0;
   
    metric_dv_update(&metric->bps, bps);

    return qe_true;
}

void qe_metric_bps_clean(qe_metric_bps *metric)
{
    metric->pts = 0;
    metric->bytes = 0;
    metric_dv_init(&metric->bps);
}

double qe_metric_bps_get(qe_metric_bps *metric)
{
    return metric_dv_get_val(&metric->bps);
}

double qe_metric_bps_get_min(qe_metric_bps *metric)
{
    return metric_dv_get_min(&metric->bps);
}

double qe_metric_bps_get_max(qe_metric_bps *metric)
{
    return metric_dv_get_max(&metric->bps);
}

qe_const_str qe_metric_bps_get_str(qe_metric_bps *metric)
{
    double bps = metric_dv_get_val(&metric->bps);
    return qe_bps_str(bps);
}

qe_const_str qe_metric_bps_get_min_str(qe_metric_bps *metric)
{
    double bps = metric->bps.min;
    return qe_bps_str(bps);
}

qe_const_str qe_metric_bps_get_max_str(qe_metric_bps *metric)
{
    double bps = metric->bps.max;
    return qe_bps_str(bps);
}

void qe_metric_per_init(qe_metric_per *metric, qe_u32 period_sec, qe_int max)
{
    metric->count = 0;
    metric->losscnt = 0;
    metric->lossval = 0;
    metric->pts = 0;
    metric->max = max;
    metric->period_sec = period_sec;
    metric_dv_init(&metric->rate);
}

qe_bool qe_metric_per_update(qe_metric_per *metric, qe_uint count)
{
    qe_uint waitcount;
    double rate;
    qe_time_t pts;
    qe_bool is_loss = qe_false;

    pts = qe_time_ms();

    if (!metric->count) {
        metric->count = count;
        return qe_false;
    }

    if (metric->count == count)
        return qe_false;

    if (metric->max < 0) {
        waitcount = metric->count + 1;
        if (count != waitcount) {
            metric->losscnt += qe_abs(count - waitcount);
            is_loss = qe_true;
        }
        metric->count = count;
    } else {
        waitcount = (metric->count + 1) % metric->max;
        if (count != waitcount) {
            if (count > waitcount) {
                metric->losscnt += count - waitcount;
                is_loss = qe_true;
            } else {
                metric->losscnt += count + metric->max - waitcount;
                is_loss = qe_true;
            }
        }
        metric->count = count;
    }

    if (!metric->pts) {
        metric->pts = pts;
        return is_loss;
    }

    if ((pts - metric->pts) < SEC_TO_MS(metric->period_sec)) {
        return is_loss;
    }

    metric->pts = pts;

    rate = (double)metric->losscnt / (double)metric->count * 100.0;
    metric->lossval += metric->losscnt;
    metric->losscnt = 0;
    metric->count = 0;
   
    metric_dv_update(&metric->rate, rate);

    return is_loss;
}

void qe_metric_per_clean(qe_metric_per *metric)
{
    metric->count = 0;
    metric->losscnt = 0;
    metric->lossval = 0;
    metric->pts = 0;
    metric_dv_init(&metric->rate);
}

qe_uint qe_metric_per_get_count(qe_metric_per *metric)
{
    return metric->lossval;
}

double qe_metric_per_get(qe_metric_per *metric)
{
    return metric_dv_get_val(&metric->rate);
}

double qe_metric_per_get_min(qe_metric_per *metric)
{
    return metric_dv_get_min(&metric->rate);
}

double qe_metric_per_get_max(qe_metric_per *metric)
{
    return metric_dv_get_max(&metric->rate);
}

qe_const_str qe_metric_per_get_str(qe_metric_per *metric)
{
    double rate = metric_dv_get_val(&metric->rate);
    return per_str(rate);
}

qe_const_str qe_metric_per_get_min_str(qe_metric_per *metric)
{
    double rate = metric_dv_get_min(&metric->rate);
    return per_str(rate);
}

qe_const_str qe_metric_per_get_max_str(qe_metric_per *metric)
{
    double rate = metric_dv_get_max(&metric->rate);
    return per_str(rate);
}

void qe_metric_delay_init(qe_metric_delay *metric)
{
    metric->pts = 0;
    metric_dv_init(&metric->delay);
}

void qe_metric_delay_clean(qe_metric_delay *metric)
{
    metric->pts = 0;
    metric_dv_init(&metric->delay);
}

void qe_metric_delay_start(qe_metric_delay *metric)
{
    metric->pts = qe_time_us();
}

void qe_metric_delay_stop(qe_metric_delay *metric)
{
    double delay;
    qe_time_t pts = qe_time_us();
    delay = (double)pts - (double)metric->pts;
    metric_dv_update(&metric->delay, delay);
}

double qe_metric_delay_get(qe_metric_delay *metric)
{
    return metric_dv_get_val(&metric->delay);
}

qe_const_str qe_metric_delay_get_str(qe_metric_delay *metric)
{
    double delay = metric_dv_get_val(&metric->delay);
    return delay_str(delay);
}

qe_const_str qe_metric_delay_get_min_str(qe_metric_delay *metric)
{
    double delay = metric_dv_get_min(&metric->delay);
    return delay_str(delay);
}

qe_const_str qe_metric_delay_get_max_str(qe_metric_delay *metric)
{
    double delay = metric_dv_get_max(&metric->delay);
    return delay_str(delay);
}

/**
 * @defgroup PPS
 */

void qe_metric_pps_init(qe_metric_pps *pps, qe_u32 period_sec)
{
    pps->pkts = 0;
    pps->pts = 0;
    pps->period_sec = period_sec;
    metric_dv_init(&pps->pps);
}

void qe_metric_pps_update(qe_metric_pps *pps, qe_u32 num)
{
    double val;
    qe_time_t pts;

    pts = qe_time_ms();

    pps->pkts += num;

    if (!pps->pts) {
        pps->pts = pts;
        return;
    }

    if ((pts - pps->pts) < SEC_TO_MS(pps->period_sec)) {
        return;
    }

    pps->pts = pts;

    val = (double)pps->pkts / (double)pps->period_sec;
    pps->pkts = 0;
   
    metric_dv_update(&pps->pps, val);
}

void qe_metric_pps_clean(qe_metric_pps *pps)
{
    pps->pts = 0;
    pps->pkts = 0;
    metric_dv_init(&pps->pps);
}

qe_const_str qe_metric_pps_get_str(qe_metric_pps *pps)
{
    double val = metric_dv_get_val(&pps->pps);
    return qe_pps_str(val);
}

qe_const_str qe_metric_pps_get_str_min(qe_metric_pps *pps)
{
    double min = metric_dv_get_min(&pps->pps);
    return qe_pps_str(min);
}

qe_const_str qe_metric_pps_get_str_max(qe_metric_pps *pps)
{
    double max = metric_dv_get_max(&pps->pps);
    return qe_pps_str(max);
}